# Copyright 2010 XDRRPC authors (see AUTHORS file)
#
# This file is part of XDRRPC.
#
# XDRRPC is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# XDRRPC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with XDRRPC.  If not, see <http://www.gnu.org/licenses/>

# Authors: Windel Bouwman and Rob Reilink
# Date: 2010

# This file implements the XDRRPC protocol in python.
# It contains both server and client side parts.

import urllib
import xdrlib
import base64
import sys

XDRRPC_NONETYPE = 0
XDRRPC_INTTYPE = 1
XDRRPC_STRINGTYPE = 2
XDRRPC_DOUBLETYPE = 3
XDRRPC_BOOLTYPE = 4
XDRRPC_LISTTYPE = 11
XDRRPC_TUPLETYPE = 12

XDRRPC_CALL = 1
XDRRPC_REPLY = 2
XDRRPC_ERROR = 3

def pack_arguments(packer, arguments):
  packer.pack_uint(len(arguments)) # store parameter count
  for p in arguments:
    if p is None:
        packet.pack_uint(XDRRPC_NONETYPE)
    elif type(p) is int:
        packer.pack_uint(XDRRPC_INTTYPE)
        packer.pack_int(p)
    elif type(p) is str:
        packer.pack_uint(XDRRPC_STRINGTYPE)
        packer.pack_string(p)
    elif type(p) is float:
        packer.pack_uint(XDRRPC_DOUBLETYPE)
        packer.pack_double(p)
    elif type(p) is bool:
        packer.pack_uint(XDRRPC_BOOLTYPE)
        packer.pack_bool(p)
    elif type(p) is list:
        packer.pack_uint(XDRRPC_LISTTYPE)
        pack_arguments(packer, p) # call recursive
    elif type(p) is tuple:
        packer.pack_uint(XDRRPC_TUPLETYPE)
        pack_arguments(packer, p) # call recursive
    else:
        raise ValueError("Unsupported type")

def unpack_arguments(unpacker):
    args=[]
    arg_count = unpacker.unpack_uint()
    for i in range(0,arg_count):
        argtype = unpacker.unpack_uint()
        if argtype == XDRRPC_NONETYPE:
            args.append(None)
        elif argtype == XDRRPC_INTTYPE:
            args.append(unpacker.unpack_int())
        elif argtype == XDRRPC_STRINGTYPE:
            args.append(unpacker.unpack_string())
        elif argtype == XDRRPC_DOUBLETYPE:
            args.append(unpacker.unpack_double())
        elif argtype == XDRRPC_BOOLTYPE:
            args.append(unpacker.unpack_bool())
        elif argtype == XDRRPC_LISTTYPE:
            args.append(unpack_arguments(unpacker)) # call recursive
        elif argtype == XDRRPC_TUPLETYPE:
            args.append(tuple(unpack_arguments(unpacker))) # call recursive
        else:
            raise ValueError("Unsupported type")
    # TODO: do not return a list of length 1
    return args

class XdrrpcException(Exception):
    def __init__(self, s):
        self.s=s
    def __str__(self):
        return repr(self.s)
    
class ServerProxy:
    def __init__(self, transport):
        self._transport = transport
    def xdrcall(self, functionname, *arguments):
        # Prepare the call:
        packer = xdrlib.Packer()
        packer.pack_uint(XDRRPC_CALL)
        packer.pack_string(functionname)
        pack_arguments(packer, arguments)
        data = packer.get_buffer()
        
        # Run over the transport
        response = self._transport.transaction(data)
        
        # Parse the response
        unpacker = xdrlib.Unpacker(response)
        msgtype = unpacker.unpack_uint()
        if (msgtype == XDRRPC_REPLY):
           arguments = unpack_arguments(unpacker)
           unpacker.done()
           if len(arguments) is 1:
               arguments = arguments[0]
           return arguments
        elif msgtype == XDRRPC_ERROR:
           errstr = unpacker.unpack_string()
           unpacker.done()
           raise XdrrpcException(errstr)
        else:
           raise IOError('message is not a reply!')
    def __getattr__(self, name): # the magic function that calls the other side
        def mynewfunction(*params):
            return self.xdrcall(name, *params)
        return mynewfunction

class Transport:
    def transaction(self, input):
        raise NotImplementedError

class HTTPTransport(Transport):
    def __init__(self, url):
        self._serverurl = url
    def transaction(self, input):
        data = base64.encodestring(input)
        u = urllib.urlopen(self._serverurl, data)
        response = u.read()
        u.close()
        response = base64.decodestring(response)
        return response

class LocalTransport(Transport):
    def __init__(self, reqhandler):
        self._reqhandler = reqhandler
    def transaction(self, input):
        return self._reqhandler.handle_request(input)

# Server part:
class BaseRequestHandler:
    def __init__(self):
        self._functions = {}
        pass
    def register_function(self, func):
        name = func.__name__
        self._functions[name] = func
    def handle_request(self, input):
        # Get the call data:
        
        unpacker=xdrlib.Unpacker(input)
        # Handle the request:
        msgtype = unpacker.unpack_uint()
        packer = xdrlib.Packer()
        if msgtype == XDRRPC_CALL:
            funcname=unpacker.unpack_string()
            arguments=unpack_arguments(unpacker)
            unpacker.done()
            if self._functions.has_key(funcname):
                thefunction = self._functions[funcname]
                try:
                    returnvars = thefunction(*arguments) # the call!
                except Exception, e:
                    packer.pack_uint(XDRRPC_ERROR)
                    packer.pack_string(str(e))
                else:
                    packer.pack_uint(XDRRPC_REPLY)
                    pack_arguments(packer, [returnvars])
            else:
                packer.pack_uint(XDRRPC_ERROR)
                packer.pack_string('function ' + funcname + ' not found')

            # Send the response:
        else:
            packer.pack_uint(XDRRPC_ERROR)
            packer.pack_string('Error: got no call message')

        response = packer.get_buffer()
        return response

class CGIRequestHandler(BaseRequestHandler):
    def __init__(self):
        BaseRequestHandler.__init__(self)
    def handle_request(self):
        input = sys.stdin.read()
        input = base64.decodestring(input)
        out = BaseRequestHandler.handle_request(self, input)
        out = base64.encodestring(out)
        print 'Content-Type: text/plain'
        print ''
        sys.stdout.write(out)

if __name__ == '__main__':
    def version():
        return 1
    def sum(a,b):
        return [a+b]
    def swap(a, b):
        return b,a
    def getImage(name):
        print name
        data = str(range(100))
        return data
    handler = BaseRequestHandler()
    handler.register_function(version)
    handler.register_function(sum)
    handler.register_function(swap)
    handler.register_function(getImage)
    t = LocalTransport(handler)
    s = ServerProxy(t)
    print s.version()
    print s.sum(3,2)
    print s.swap(5,6)
    print s.getImage('x.png')
